#include "Module.hpp"
#include "HelperFunctions.hpp"
#include <iostream>
#include "ModuleManager.hpp"

namespace lua
{

Module *Module::currentModule = nullptr;

static void report_errors(Module *module, int status)
{
	if(status != 0)
	{
		std::cerr<<"Error: "<<module->getName()<<"/"<<lua_tostring(module->getLuaVM(), -1)<<std::endl;
		lua_pop(module->getLuaVM(), 1); // remove error message from stack
	}
}

int panic(lua_State *luaVM)
{
	std::cerr<<lua_tostring(luaVM, -1)<<std::endl;
	lua_pop(luaVM, 1); // remove error message from stack
	std::cin.get();
	return 0;
}

int luaCallCFunction(lua_State *luaVM)
{
	_lua_CFunction func = (_lua_CFunction)lua_topointer(luaVM, lua_upvalueindex(1));
	lua::Module *module = (lua::Module*)lua_topointer(luaVM, lua_upvalueindex(2));
	lua_remove(luaVM, 1);

	Module::currentModule = module;
	Args args(module);
	Return ret(func(module, args));
	if(ret.getModule() != module)
	{
		for(int i = 1; i <= ret.getNumReturnValues(); ++i)
		{
			lua::pushCopy(ret.getModule()->getLuaVM(), -i, luaVM);
		}
	}
	return ret.getNumReturnValues();
	return 0;
}

Module::Module(const char *name) : 
	mName(name),
	mLuaVM(lua_open()),
	mEntityManager()
{
	Module::currentModule = this;
	lua_atpanic(mLuaVM, panic);
	ModuleManager::getInstance().add(this);
}

Module* Module::create(const char *name)
{ 
	return new Module(name);
}

bool Module::destroy()
{ 
	bool removed = ModuleManager::getInstance().remove(this);
	delete this;
	return removed;
}

void Module::loadDefaultLibraries(bool os, bool io, bool base, bool table, bool string, bool math)
{
	Module::currentModule = this;
	if(os)
		luaopen_os(mLuaVM);
	if(io)
		luaopen_io(mLuaVM);
	if(base)
		luaopen_base(mLuaVM);
	if(table)
		luaopen_table(mLuaVM);
	if(string)
		luaopen_string(mLuaVM);
	if(math)
		luaopen_math(mLuaVM);
	clearStack();
}

void Module::registerFunction(const char *libName, const char *funcName, _lua_CFunction func)
{
	//Module::currentModule = this;
	//lua_pushlightuserdata(mLuaVM, func);
	//lua_pushlightuserdata(mLuaVM, this);
	//lua_pushcclosure(mLuaVM, luaCallCFunction, 2);
	//lua_setglobal(mLuaVM, name);

	Module::currentModule = this;
	lua_getfield(mLuaVM, LUA_GLOBALSINDEX, libName);
	if(!lua_istable(mLuaVM, -1))
	{
		lua_newtable(mLuaVM);
		lua_setfield(mLuaVM, LUA_GLOBALSINDEX, libName);

		lua_pop(mLuaVM, 1);
		lua_getfield(mLuaVM, LUA_GLOBALSINDEX, libName);
	}
	lua_pushstring(mLuaVM, funcName);
	lua_pushlightuserdata(mLuaVM, func);
	lua_pushlightuserdata(mLuaVM, this);
	lua_pushcclosure(mLuaVM, luaCallCFunction, 2);
	lua_settable(mLuaVM, -3);
	//lua_pop(mLuaVM, 1);
	clearStack();
}

bool Module::loadFile(const char *filename)
{
	Module::currentModule = this;
	int status = luaL_loadfile(mLuaVM, filename);
	if(status == 0)
	{
		status = lua_pcall(mLuaVM, 0, LUA_MULTRET, 0);
		if(status == 0)
		{
			clearStack();
			return true;
		}
	}
	report_errors(this, status);
	clearStack();
	return false;
}

bool Module::dostring(const char *str)
{
	Module::currentModule = this;
	int status = luaL_dostring(mLuaVM, str);
	if(status == 0)
		return true;
	report_errors(this, status);
	return false;
}

Return Module::callLuaFunction(const char *name, Args args)
{
	if(this != args.getModule())
	{
		for(int i = 1; i <= args.getNumArgs(); ++i)
		{
			lua::pushCopy(args.getModule()->getLuaVM(), -i, mLuaVM);
		}
		Module::currentModule = this;
		lua_getfield(mLuaVM, LUA_GLOBALSINDEX, name);
		lua::move(mLuaVM, -(args.getNumArgs()+1));
		int status = lua_pcall(mLuaVM, args.getNumArgs(), LUA_MULTRET, 0);
		if(status == 0)
		{
			clearStack();
			return Return(args.getNumArgs());
		}
		report_errors(this, status);
		clearStack();
		return Return();
	}
	else
	{
		int stackSizeBefore = lua_gettop(mLuaVM);
		Module::currentModule = this;
		lua_getfield(mLuaVM, LUA_GLOBALSINDEX, name);
		lua::move(mLuaVM, -(args.getNumArgs()+1));
		int status = lua_pcall(mLuaVM, args.getNumArgs(), LUA_MULTRET, 0);
		if(status == 0)
		{
			clearStack();
			return Return(lua_gettop(mLuaVM) - stackSizeBefore);
		}
		report_errors(this, status);
		clearStack();
		return Return();
	}
}

void Module::clearStack()
{
	lua_settop(mLuaVM, 0);
}

Module::~Module()
{
	lua_close(mLuaVM);
	if(Module::currentModule == this)
		Module::currentModule = nullptr;
}

}